package cn.nexttec.noteplayer.viewmodels

import android.content.Context
import android.os.Environment
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import cn.nexttec.noteplayer.BuildConfig
import cn.nexttec.noteplayer.common.AppendFileWriter
import cn.nexttec.noteplayer.common.AudioDecoderToPCM
import cn.nexttec.noteplayer.common.FFmpegUtil
import cn.nexttec.noteplayer.common.Preference
import cn.nexttec.noteplayer.common.debug
import cn.nexttec.noteplayer.common.gson
import cn.nexttec.noteplayer.common.md5
import cn.nexttec.noteplayer.databases.dao.NoteDao
import cn.nexttec.noteplayer.databases.dao.PlayRecordDao
import cn.nexttec.noteplayer.databases.dao.VideoFileDao
import cn.nexttec.noteplayer.injection.IoDispatcher
import cn.nexttec.noteplayer.injection.MainDispatcher
import cn.nexttec.noteplayer.models.NoteFile
import cn.nexttec.noteplayer.models.PlayRecord
import cn.nexttec.noteplayer.models.SortMethod
import cn.nexttec.noteplayer.models.VideoFileInfo
import cn.nexttec.noteplayer.models.VideoFileInfo.Companion.sort
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.Date
import javax.inject.Inject
import javax.inject.Named

@HiltViewModel
class PlayViewModel @Inject constructor(
    @ApplicationContext val context: Context,
    val noteDao: NoteDao,
    val videoDao: VideoFileDao,
    val recordDao: PlayRecordDao,
    val preference: Preference,
    @Named("BaseDir") val noteDir: File,
    @Named("TempDir") val tempDir: File,
    @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
    @MainDispatcher private val mainDispatcher: CoroutineDispatcher
) : ViewModel() {

    private var noteFile: NoteFile? = null
    var isPlaying = false
    private var lastState = true
    private var currentVideo: VideoFileInfo? = null
    private var record: PlayRecord? = null
    private var _playlist = MutableLiveData<List<VideoFileInfo>>()
    val playlist: LiveData<List<VideoFileInfo>> get() = _playlist

    private val _playPosition = MutableLiveData<Long>(0L)
    val playPosition: LiveData<Long> get() = _playPosition
    private val _nextVideo = MutableLiveData<VideoFileInfo>()
    val nextVideo: LiveData<VideoFileInfo> get() = _nextVideo
    private val _note = MutableLiveData<NoteFile?>()
    private var noteWriter:AppendFileWriter? = null

    private val _cropResult = MutableLiveData<RecordResult>(RecordResult.Initial)
    val cropResult get() = _cropResult

    private val _screenShotResult = MutableLiveData<ScreenShotResult>(ScreenShotResult.Initial)
    val screenShotResult get() = _screenShotResult

    val note: LiveData<NoteFile?> get() = _note
    var markdownFile: File? = null

    companion object {
        private const val TAG = "PlayerActivity"
    }

    fun loadPlayList(videoInfo: VideoFileInfo) {
        if (preference.playList.isNullOrEmpty()) {
            viewModelScope.launch(ioDispatcher) {
                val files = videoDao.getVideoFilesByPath(videoInfo.parent)
                val videos = files.sort(SortMethod.fromInt(preference.sortMethod)!!)
                withContext(Dispatchers.Main) {
                    _playlist.value = videos
                    var index = files.indexOf(videoInfo) + 1
                    if (index == files.size) {
                        index = 0
                    }
                    _nextVideo.value = videos[index]
                }
            }
        }
    }

    fun getNoteFile(videoInfo: VideoFileInfo) {
        viewModelScope.launch(ioDispatcher) {
            val _noteFile = noteDao.getNoteByVideoMd5(videoInfo.videoMd5)
            _noteFile?.let {
                noteWriter = AppendFileWriter(File(it.filepath))
                withContext(Dispatchers.Main) {
                    _note.value = _noteFile
                    noteFile = _noteFile
                }
            }
        }
    }

    fun readPlayRecord(videoInfo: VideoFileInfo) {
        currentVideo = videoInfo
        viewModelScope.launch(ioDispatcher) {
            record = recordDao.getRecord(videoInfo.videoMd5)
            if (record == null) {
                record = PlayRecord().apply {
                    videoMd5 = videoInfo.videoMd5
                    lastPlayTime = Date().time
                }.also {
                    recordDao.insert(it)
                }
            } else {
                record!!.lastPlayTime = Date().time
                TAG debug "读取到Record:${record!!.gson()}"
                recordDao.updateRecord(record!!)
            }
            withContext(mainDispatcher) {
                _playPosition.value = record!!.currentPosition
                TAG debug "ViewModel: ReadPosition: ${record!!.currentPosition}"
            }
        }
    }

    fun loadPlayList() {
        viewModelScope.launch(ioDispatcher) {
            if (preference.playList.isNullOrEmpty()) {
                val videos = videoDao.getVideoFilesByPath(currentVideo!!.path)

            }
        }
    }


    fun updatePlayRecord(curPosition: Long) {
        viewModelScope.launch(ioDispatcher) {
            record?.let {
                it.currentPosition = curPosition
                TAG debug "写入Record:${record!!.gson()}"
                recordDao.updateRecord(record!!)
            }
        }
    }

    fun resetRecord(videoInfo: VideoFileInfo) {
        viewModelScope.launch(ioDispatcher) {
            record?.let {
                it.currentPosition = 0L
                recordDao.updateRecord(record!!)
            }
        }
    }

    fun onAudioClicked() {
        preference.enableAudioMode = !preference.enableAudioMode
    }


    fun extractAudio(video: VideoFileInfo) {
        val cacheDir = context.cacheDir.absolutePath
        var voskDir: File
        if (BuildConfig.BUILD_TYPE == "release") {
            voskDir = File(cacheDir, "Vosk")
            if (voskDir.exists().not()) {
                voskDir.mkdirs()
            }
        } else {
            voskDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        }
        Log.d("extractAudio", "音频提取: ${voskDir.absolutePath}")
        val outputName = "${video.videoMd5}.pcm"
        val outputFile = File(voskDir, outputName)
        viewModelScope.launch(ioDispatcher) {
            try {
                AudioDecoderToPCM.decodeToPCM(video.path, outputFile.absolutePath)
                Log.d("extractAudio", "音频提取完成: ${outputFile.absolutePath}")
                if (preference.voskModelPath == null) {
                    Log.d("extractAudio", "Vosk模型未安装")
                } else {
//                    val srtContent = VoskSrtGenerator.convertPcmToSrt(
//                        outputFile,
//                        preference.voskModelPath!!
//                    )
                    Log.d("extractAudio", "字幕文件生成成功")

                    // 写入同目录下同名.srt文件
//                    val srtFile = File(outputFile.parentFile, "${video.nameWithoutExension}.srt")
//                    srtFile.writeText(srtContent)
//                    Log.d("extractAudio", "字幕文件保存路径: ${srtFile.absolutePath}")
                }

            } catch (e: Exception) {
                Log.e("extractAudio", "音频提取失败", e)
            }
        }
    }

    fun updateVideoInfo(videoInfo: VideoFileInfo) {
        viewModelScope.launch(ioDispatcher) {
            videoDao.updateVideo(videoInfo)
        }
    }

    // 递归复制 assets 目录中文件到目标目录
    /**
     * 递归从 assets 目录拷贝指定路径内容到目标文件夹
     * @param context  Context
     * @param assetPath assets内的目录路径，如 "html"
     * @param destDir  目标文件夹
     */
    fun copyAssetsDirTo(assetPath: String, destDir: File) {
        try {
            val assets = context.assets.list(assetPath)
            if (assets == null || assets.isEmpty()) {
                // 是文件，拷贝文件
                copyAssetFile(assetPath, File(destDir, assetPath.substringAfterLast('/')))
            } else {
                // 是目录，递归处理
                val targetDir = if (assetPath.contains('/')) {
                    File(destDir, assetPath.substringAfterLast('/'))
                } else {
                    destDir
                }
                if (!targetDir.exists()) targetDir.mkdirs()
                for (child in assets) {
                    val childAssetPath = if (assetPath.isEmpty()) child else "$assetPath/$child"
                    copyAssetsDirTo(childAssetPath, targetDir)
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    /**
     * 拷贝单个 assets 文件到目标文件
     */
    fun copyAssetFile(assetFilePath: String, destFile: File) {
        context.assets.open(assetFilePath).use { inputStream ->
            FileOutputStream(destFile).use { outputStream ->
                val buffer = ByteArray(4096)
                var read = inputStream.read(buffer)
                while (read != -1) {
                    outputStream.write(buffer, 0, read)
                    read = inputStream.read(buffer)
                }
                outputStream.flush()
            }
        }
    }


    fun createNote(videoInfo: VideoFileInfo) {
        viewModelScope.launch(ioDispatcher) {
            if (noteDir.exists().not()){
                noteDir.mkdirs()
            }
            val path = File(noteDir, videoInfo.videoMd5)
            if (path.exists().not()) {
                path.mkdirs()
            }

            val mediaPath = File(path, "media")
            if (mediaPath.exists().not()) {
                mediaPath.mkdirs()
            }

            // 复制 assets/html 到笔记目录，确保 preview.html、js、css 都存在
            copyAssetsDirTo("html", File(path, "html"))


            val noteFilename = "Note_${videoInfo.videoMd5}.md"
            markdownFile = File(path, noteFilename)
            if (!markdownFile!!.exists()) {
                markdownFile!!.createNewFile()
            }
            noteWriter = AppendFileWriter(markdownFile!!)
            noteWriter!!.writeLine("# ${videoInfo.nameWithoutExension}\n\n---\n\n")
            noteFile = NoteFile().apply {
                folder = path.absolutePath
                name = videoInfo.nameWithoutExension
                videoMd5 = videoInfo.videoMd5
                filepath = markdownFile!!.absolutePath
            }
            noteDao.createNote(noteFile!!)
            withContext(mainDispatcher) {
                _note.value = noteFile
            }
        }
    }

    fun writeToNote(text: String) {
        viewModelScope.launch(ioDispatcher) {
            try {
                noteWriter!!.writeLine(text)
                noteWriter!!.writeLine("\n\n")
            } catch (e:Exception){
                TAG debug e.stackTraceToString()
            }
        }
    }

    fun stopRecord(videoInfo: VideoFileInfo?, recordStartPos: Long, endPosition: Long) {
        viewModelScope.launch(ioDispatcher) {

            val videoName = "${videoInfo!!.path}_${recordStartPos}_$endPosition".md5()
            val destPath = File(noteFile!!.folder, "html/videos")
            if (destPath.exists().not()) {
                destPath.mkdirs()
            }
            val duration = endPosition - recordStartPos
            val destFile = File(destPath, "${videoName}.mp4")
            try {
                FFmpegUtil.clip(
                    videoInfo.path,
                    recordStartPos,
                    duration,
                    destFile.absolutePath,
                    onSuccess = {
                        viewModelScope.launch(mainDispatcher) {
                            _cropResult.value =
                                RecordResult.Success(
                                    destFile.absolutePath,
                                    recordStartPos,
                                    endPosition
                                )
                        }
                    },
                    onError = {
                        viewModelScope.launch(mainDispatcher) {
                            _cropResult.value = RecordResult.Fail(it)
                        }
                    })
            } catch (e: Exception) {
                viewModelScope.launch(mainDispatcher) {
                    _cropResult.value = RecordResult.Fail(e.localizedMessage)
                    throw e
                }
            }
        }

    }


    fun screenshot(videoInfo: VideoFileInfo, currentPosition: Long) {
        viewModelScope.launch(ioDispatcher) {
            val tempImageFile = File(tempDir, "${videoInfo.videoMd5}_${currentPosition}.jpg")
            FFmpegUtil.captureFrame(videoInfo.path, currentPosition, tempImageFile.absolutePath,
                onSuccess = {
                    viewModelScope.launch(mainDispatcher) {
                        _screenShotResult.value =
                            ScreenShotResult.Success(tempImageFile.absolutePath)
                    }
                },
                onError = {
                    _screenShotResult.value = ScreenShotResult.Fail(it)
                }
            )
        }
    }

    fun favorite(videoInfo: VideoFileInfo, currentPosition: Long) {
        viewModelScope.launch(ioDispatcher) {
            val tempImageFile = File(tempDir, "${videoInfo.videoMd5}_${currentPosition}.jpg")
            FFmpegUtil.captureFrame(videoInfo.path, currentPosition, tempImageFile.absolutePath,
                onSuccess = {
                    viewModelScope.launch(mainDispatcher) {
                        _screenShotResult.value =
                            ScreenShotResult.Favorite(tempImageFile.absolutePath)
                    }
                },
                onError = {
                    _screenShotResult.value = ScreenShotResult.Fail(it)
                }
            )
        }
    }

    sealed class RecordResult {
        data object Initial : RecordResult()
        data class Success(val videoPath: String, val startPos: Long, val endPos: Long) :
            RecordResult()

        data class Fail(val message: String) : RecordResult()

    }

    sealed class ScreenShotResult {
        data object Initial : ScreenShotResult()
        data class Success(val imagePath: String) : ScreenShotResult()
        data class Favorite(val imagePath: String) : ScreenShotResult()
        data class Fail(val message: String) : ScreenShotResult()
    }


}